/************************************************************************
* events_x11.c
* voxelands - 3d voxel world sandbox game
* Copyright (C) Lisa 'darkrose' Milne 2016 <lisa@ltmnet.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
************************************************************************/

#include "common.h"
#define _WM_EXPOSE_ALL
#include "wm.h"

#ifndef WIN32

#include <wctype.h>
#include <string.h>

int xkey_to_char(uint32_t *ch, XEvent *event)
{
	KeySym keysym;
	wchar_t buff[6];
	char* kc;
	int l;
	l = XwcLookupString(wm_data.ic,&event->xkey,buff,4,&keysym,NULL);
	if (l == 1) {
		*ch = buff[0];
	}else{
		kc = XKeysymToString(keysym);
		if (kc && utf8_strlen(kc) == 1)
			*ch = utf8_toutf32(kc,-1);
	}

	return 0;
}

int xevent_to_sym(sym_t *s, XEvent *event)
{
	int sym;
	char* kc;

	sym = XLookupKeysym(&event->xkey, 0);

	switch (sym) {
	case XK_Shift_L:
	case XK_Shift_R:
		s->type = SYM_TYPE_MOD;
		s->sym = SYM_MOD_SHIFT;
		break;
	case XK_Control_L:
	case XK_Control_R:
		s->type = SYM_TYPE_MOD;
		s->sym = SYM_MOD_CTRL;
		break;
	case XK_Alt_L:
	case XK_Alt_R:
		s->type = SYM_TYPE_MOD;
		s->sym = SYM_MOD_ALT;
		break;
	case XK_Super_L:
	case XK_Super_R:
		s->type = SYM_TYPE_MOD;
		s->sym = SYM_MOD_SUPER;
		break;
	case XK_space:
	case XK_KP_Space:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_SPACE;
		break;
	case XK_Escape:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_ESCAPE;
		break;
	case XK_Tab:
	case XK_KP_Tab:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_TAB;
		break;
	case XK_KP_0:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_KP0;
		break;
	case XK_KP_1:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_KP1;
		break;
	case XK_KP_2:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_KP2;
		break;
	case XK_KP_3:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_KP3;
		break;
	case XK_KP_4:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_KP4;
		break;
	case XK_KP_5:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_KP5;
		break;
	case XK_KP_6:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_KP6;
		break;
	case XK_KP_7:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_KP7;
		break;
	case XK_KP_8:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_KP8;
		break;
	case XK_KP_9:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_KP9;
		break;
	case XK_Left:
	case XK_KP_Left:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_LEFT;
		break;
	case XK_Up:
	case XK_KP_Up:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_UP;
		break;
	case XK_Right:
	case XK_KP_Right:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_RIGHT;
		break;
	case XK_Down:
	case XK_KP_Down:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_DOWN;
		break;
	case XK_F1:
	case XK_KP_F1:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_F1;
		break;
	case XK_F2:
	case XK_KP_F2:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_F2;
		break;
	case XK_F3:
	case XK_KP_F3:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_F3;
		break;
	case XK_F4:
	case XK_KP_F4:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_F4;
		break;
	case XK_F5:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_F5;
		break;
	case XK_F6:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_F6;
		break;
	case XK_F7:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_F7;
		break;
	case XK_F8:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_F8;
		break;
	case XK_F9:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_F9;
		break;
	case XK_F10:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_F10;
		break;
	case XK_F11:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_F11;
		break;
	case XK_F12:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_F12;
		break;
	case XK_BackSpace:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_BCKSPC;
		break;
	case XK_Return:
	case XK_KP_Enter:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_RETURN;
		break;
	case XK_Delete:
	case XK_KP_Delete:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_DELETE;
		break;
	case XK_KP_Equal:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_KPEQ;
		break;
	case XK_KP_Multiply:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_KPMUL;
		break;
	case XK_KP_Add:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_KPADD;
		break;
	case XK_KP_Subtract:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_KPSUB;
		break;
	case XK_KP_Decimal:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_KPDOT;
		break;
	case XK_KP_Divide:
		s->type = SYM_TYPE_SKEY;
		s->sym = SYM_KEY_KPDIV;
		break;
/* stuff we don't care about */
	case XK_Linefeed:
	case XK_Clear:
	case XK_Pause:
	case XK_Scroll_Lock:
	case XK_Sys_Req:
	case XK_Home:
	case XK_Prior:
/*	case XK_KP_Prior: same as non-kp
	case XK_Page_Up: same as Prior
	case XK_KP_Page_Up: same as non-kp */
	case XK_Next:
/*	case XK_KP_Next: same as non-kp
	case XK_Page_Down: same as Next
	case XK_KP_Page_Down: same as non-kp */
	case XK_Mode_switch:
/*	case XK_script_switch: same as Mode_switch */
	case XK_End:
	case XK_Begin:
	case XK_Select:
	case XK_Print:
	case XK_Execute:
	case XK_Insert:
	case XK_Undo:
	case XK_Redo:
	case XK_Menu:
	case XK_Find:
	case XK_Cancel:
	case XK_Help:
	case XK_Break:
	case XK_Num_Lock:
	case XK_KP_Home:
	case XK_KP_End:
	case XK_KP_Begin:
	case XK_KP_Insert:
		break;
	default:
		s->type = SYM_TYPE_KEY;
		kc = XKeysymToString(sym);
		if (kc && utf8_strlen(kc) == 1) {
			s->sym = utf8_toutf32(kc,-1);
			if (!iswgraph(s->sym))
				s->sym = 0;
		}
	}

	if (!s->sym) {
		char buff[5];
		int l;
		KeySym keysym;
		l = XLookupString(&event->xkey, buff, 5, &keysym, NULL);
		if (l>0 && utf8_strlen(buff) == 1)
			s->sym = utf8_toutf32(buff,-1);
	}

	return 0;
}

/* read and act on events */
void events_main()
{
	XEvent event;
	event_t e;
	int rm[2];
	while (XPending(wm_data.dpy) > 0) {
		XNextEvent(wm_data.dpy, &event);
		if (XFilterEvent(&event,None) == True)
			continue;
		e.type = EVENT_NONE;
		e.sym.type = SYM_TYPE_NONE;
		e.sym.sym = 0;
		e.sym.ch = 0;
		switch (event.type) {
		case Expose:
			if (event.xexpose.count != 0)
				break;
			break;
		case ConfigureNotify:
			if ((event.xconfigure.width != wm_data.size.width) || (event.xconfigure.height != wm_data.size.height)) {
				wm_data.size.width = event.xconfigure.width;
				wm_data.size.height = event.xconfigure.height;
				wm_resize();
			}
			break;
		case ButtonPress:
			e.type = EVENT_BUTTON_DOWN;
			e.sym.type = SYM_TYPE_MOUSE;
			if (event.xbutton.button == 1) {
				e.sym.sym = MOUSE_BUTTON_LEFT;
				events_handle(&e);
			}else if (event.xbutton.button == 2) {
				e.sym.sym = MOUSE_BUTTON_CENTRE;
				events_handle(&e);
			}else if (event.xbutton.button == 3) {
				e.sym.sym = MOUSE_BUTTON_RIGHT;
				events_handle(&e);
			}else if (event.xbutton.button == 4) {
				e.sym.sym = MOUSE_BUTTON_UP;
				events_handle(&e);
			}else if (event.xbutton.button == 5) {
				e.sym.sym = MOUSE_BUTTON_DOWN;
				events_handle(&e);
			}
			break;
		case ButtonRelease:
			e.type = EVENT_BUTTON_UP;
			e.sym.type = SYM_TYPE_MOUSE;
			if (event.xbutton.button == 1) {
				e.sym.sym = MOUSE_BUTTON_LEFT;
				events_handle(&e);
			}else if (event.xbutton.button == 2) {
				e.sym.sym = MOUSE_BUTTON_CENTRE;
				events_handle(&e);
			}else if (event.xbutton.button == 3) {
				e.sym.sym = MOUSE_BUTTON_RIGHT;
				events_handle(&e);
			}
			break;
		case MotionNotify:
			e.type = EVENT_MOUSE_MOTION;
			e.x = event.xmotion.x;
			e.y = event.xmotion.y;
			events_get_mouse(rm);
			e.rx = e.x-rm[0];
			e.ry = e.y-rm[1];
			e.sym.type = SYM_TYPE_MOUSE;
			e.sym.sym = MOUSE_MOTION;
			events_set_mouse(e.x,e.y);
			if (e.rx > -100 && e.rx < 100 && e.ry > -100 && e.ry < 100) {
				events_handle(&e);
				if (events_get_mousegrab()) {
					if (e.x < 5 || e.x > (wm_data.size.width-5))
						XWarpPointer(wm_data.dpy, None, wm_data.win, 0, 0, 0, 0, wm_data.size.width/2, e.y);
					if (e.y < 5 || e.y > (wm_data.size.height-5))
						XWarpPointer(wm_data.dpy, None, wm_data.win, 0, 0, 0, 0, e.x, wm_data.size.height/2);
				}
			}
			break;
		case KeyPress:
			e.type = EVENT_KEY_DOWN;
			xevent_to_sym(&e.sym,&event);
			xkey_to_char(&e.sym.ch,&event);
			if (e.sym.sym)
				events_handle(&e);
			break;
		case KeyRelease:
			if (XEventsQueued(wm_data.dpy, QueuedAfterReading)) {
				XEvent nev;
				XPeekEvent(wm_data.dpy, &nev);
				if (
					nev.type == KeyPress
					&& nev.xkey.time == event.xkey.time
					&& nev.xkey.keycode == event.xkey.keycode
				) {
					break;
				}
			}
			e.type = EVENT_KEY_UP;
			xevent_to_sym(&e.sym,&event);
			if (e.sym.sym)
				events_handle(&e);
			break;
		case MappingNotify:
			XRefreshKeyboardMapping(&event.xmapping);
			break;
		case ClientMessage:
			if (strstr(XGetAtomName(wm_data.dpy, event.xclient.message_type),"WM_PROTOCOLS"))
				client_state(VLSTATE_EXIT);
			break;
		default:;
		}
	}
	events_trigger_active();
}
#endif
